%%% -------------------------------------------------------------------
%%% Author  : Administrator
%%% Description :
%%%
%%% Created : 2009-3-20
%%% -------------------------------------------------------------------
-module(ring_accessor).

-behaviour(gen_server).
%% --------------------------------------------------------------------
%% Include files
%% --------------------------------------------------------------------
-include("ring.hrl").
-include("map.hrl").

%% --------------------------------------------------------------------
%% External exports
-export([start_link/1]).

-export([list_ringnodes/0, get_from_ring/1, put_to_ring/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-record(state, {ringnode_tab}).

%% ====================================================================
%% External functions
%% ====================================================================
-spec start_link(RingLord :: node()) -> any().
start_link(RingLord) ->
    M = build_ringaccessor(),
    Me = M#ring_accessor{name=?MODULE}, % for simple get_from_ring(Key) & put_to_ring(...)
    gen_server:start_link({local, Me#ring_accessor.name}, ?MODULE, [Me, RingLord], []).

-spec get_from_ring(Key :: non_neg_integer()) -> cell().
get_from_ring(Key) ->
    gen_server:call(?MODULE, {get, Key}).

put_to_ring(Key, Value) ->
    gen_server:call(?MODULE, {put, Key, Value}).

list_ringnodes() ->
    gen_server:call(?MODULE, {list_ringnodes}).

%% ====================================================================
%% Server functions
%% ====================================================================

%% --------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%% --------------------------------------------------------------------
init([Me, RingLord]) ->
    process_flag(trap_exit, true),
    
    net_kernel:connect_node(RingLord),
    ring_lord:add_ringaccessor(Me),
    
    RingNodeTab = ets:new(ring_nodes, [ordered_set, protected, {keypos, #ring_node.id}]),
    RingNodes = ring_lord:get_ringnodes(),
    ets:insert(RingNodeTab, RingNodes),
    
    {ok, #state{ringnode_tab = RingNodeTab}}.

build_ringaccessor() ->
    Node = node(),
    Id = app_util:getRandomId(),
    Name = list_to_atom("ring_accessor_" ++ integer_to_list(Id)),
    #ring_accessor{
        id = Id,
        node = Node,
        name = Name
    }.
    
%% --------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%% --------------------------------------------------------------------
handle_call({list_ringnodes}, _From, #state{ringnode_tab = RingNodeTab} = State) ->
    io:format("ringnode_tab: ~p~n", [RingNodeTab]),
    Reply = ets:tab2list(RingNodeTab),
    {reply, Reply, State};
handle_call({update_ringnodes, RingNodes}, _From, #state{ringnode_tab = RingNodeTab} = State) ->
    io:format("ringnode_tab: ~p~n", [RingNodes]),
    ets:delete_all_objects(RingNodeTab),
    ets:insert(RingNodeTab, RingNodes),
    {reply, ok, State};
handle_call({get, Key}, From, #state{ringnode_tab = RingNodeTab} = State) ->
    RingNodeId = orientate_node(RingNodeTab, Key),
    [RingNode] = ets:lookup(RingNodeTab, RingNodeId),
    #ring_node{ip=Ip,access_port=Port} = RingNode,
    spawn_link(fun() -> CellValue = get_from_store(Ip, Port, Key), gen_server:reply(From, CellValue) end),
    {noreply, State};
handle_call({put, Key, Value}, From, #state{ringnode_tab = RingNodeTab} = State) ->
    RingNodeId = orientate_node(RingNodeTab, Key),
    [RingNode] = ets:lookup(RingNodeTab, RingNodeId),
    #ring_node{ip=Ip,access_port=Port} = RingNode,
    spawn_link(fun() -> Reply = put_to_store(Ip, Port, Key, Value), gen_server:reply(From, Reply) end),
    {noreply, State}.

%% --------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%% --------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%% --------------------------------------------------------------------
%% Function: handle_info/2
%% Description: Handling all non call/cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%% --------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%% --------------------------------------------------------------------
%% Function: terminate/2
%% Description: Shutdown the server
%% Returns: any (ignored by gen_server)
%% --------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%% --------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%% --------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% --------------------------------------------------------------------
%%% Internal functions
%% --------------------------------------------------------------------
orientate_node(RingNodeTab, Key) ->
    case ets:next(RingNodeTab, Key) of
        '$end_of_table' ->
            ets:first(RingNodeTab);
        Key2 ->
            Key2
    end.

get_from_store(Ip, Port, Key) ->
    {ok, Socket} = gen_tcp:connect(Ip, Port, [binary, {packet,2}]),
    ok = gen_tcp:send(Socket, term_to_binary({get, Key})),
    receive
        {tcp, Socket, Bin} ->
            binary_to_term(Bin);
        {tcp_closed, Socket} ->
            throw({error, tcp_closed})
    end.

put_to_store(Ip, Port, Key, Value) ->
    {ok, Socket} = gen_tcp:connect(Ip, Port, [binary, {packet,2}]),
    ok = gen_tcp:send(Socket, term_to_binary({put, Key, Value})),
    receive
        {tcp, Socket, Bin} ->
            binary_to_term(Bin);
        {tcp_closed, Socket} ->
            log4erl:warn("Socket closed")
    end.

